#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#include <linux/sched.h>
#include <linux/proc_fs.h>

#include <linux/kfifo.h>
#include <linux/moduleparam.h>

#include <linux/semaphore.h>
#include <linux/wait.h>



MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Saeed Mahameed");
MODULE_DESCRIPTION("Storas - realtime data storing util");
MODULE_VERSION("0.1-test");
//MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS("storas");

static int fifoSize=1024;

module_param(fifoSize, int, S_IRUGO);

#define MOD_NAME "storas"

#define DBGLVL KERN_ALERT
#define WHERESTR  "[file %s, line %d]: "
#define WHEREARG  __FILE__, __LINE__
#define KLOG(_fmt, ...) printk(DBGLVL "[%s] " _fmt, MOD_NAME, ##__VA_ARGS__)

#define DATA_SIZE 1000

struct storas_dev {
	void* data;
	struct cdev cdev;
	unsigned int size;
	unsigned int hi;
	unsigned int lo;
        struct kfifo gfifo;
	struct semaphore lockGfifo;
	wait_queue_head_t rq;
	wait_queue_head_t wq;
	
	unsigned long readnum;
	unsigned long writenum;
	
	//	DECLARE_KFIFO(gfifo, fifoSize);
} devs;

static dev_t devt ;


static unsigned int major = 50;
static unsigned int minor = 0;


int 	mod_open(struct inode *inode, struct file *filp);

int 	mod_release(struct inode *inode, struct file *filp);

ssize_t mod_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos);

ssize_t mod_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos);

struct file_operations my_fops = {
	.owner =	THIS_MODULE,
//	.llseek =	my_llseek,
	.read =		mod_read,
	.write =	mod_write,	
//	.ioctl =	my_ioctl,	
	.open =		mod_open,
	.release = 	mod_release,
};


int mod_open(struct inode *inode, struct file *filp)
{
	struct storas_dev *dev; /* device information */
	dev = container_of(inode->i_cdev, struct storas_dev, cdev);
	filp->private_data = dev; /* for other methods */
	KLOG("Device is being opened by %s [%d]\n",current->comm,current->pid);
	KLOG("OPEN SUCCESS\n");
	return 0;
	/* success */
}

int mod_release(struct inode *inode, struct file *filp)
{
	
	KLOG("CLOSE SUCCESS\n");
	return 0;
}

ssize_t mod_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
	
	ssize_t retval = -ENOMEM,total; /* value used in "goto out" statements */
	struct storas_dev *dev = filp->private_data;
	
	if (down_interruptible(&dev->lockGfifo))
		return -ERESTARTSYS;
	while (kfifo_len(&devs.gfifo) == 0) { /* nothing to read */
		up(&dev->lockGfifo); /* release the lock */
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		KLOG("\"%s\" reading: going to sleep\n", current->comm);
		if (wait_event_interruptible(dev->rq, kfifo_len(&devs.gfifo) != 0))
			return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
		/* otherwise loop, but first reacquire the lock */
		if (down_interruptible(&dev->lockGfifo))
			return -ERESTARTSYS;
	}

        KLOG("to read %d\n",count); 
	retval =  kfifo_to_user(&dev->gfifo,buf,count,&total);
	KLOG("copied %d , left %d\n",total,retval);
       	KLOG("gFIFO %d:%d\n",kfifo_len(&dev->gfifo),kfifo_size(&dev->gfifo));
	dev->readnum ++;
	up(&dev->lockGfifo);
	/* finally, awake any writers and return */
	wake_up_interruptible(&dev->wq);
	return retval?retval:total;
}

ssize_t mod_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
	struct storas_dev *dev = filp->private_data;
	ssize_t retval = -ENOMEM,total; /* value used in "goto out" statements */
	KLOG("to write %d\n",count);
	if (down_interruptible(&dev->lockGfifo))
                return -ERESTARTSYS;
        while (kfifo_len(&devs.gfifo) == kfifo_size(&devs.gfifo)) { /* nowhere to write */
                up(&dev->lockGfifo); /* release the lock */
                if (filp->f_flags & O_NONBLOCK)
                        return -EAGAIN;
                KLOG("\"%s\" writing: going to sleep\n", current->comm);
                if (wait_event_interruptible(dev->wq, kfifo_len(&devs.gfifo) <  kfifo_size(&devs.gfifo)))
                        return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
                /* otherwise loop, but first reacquire the lock */
                if (down_interruptible(&dev->lockGfifo))
                        return -ERESTARTSYS;
        }


	retval = kfifo_from_user(&dev->gfifo,buf, count, &total);
	KLOG("gFIFO %d:%d\n",kfifo_len(&dev->gfifo),kfifo_size(&dev->gfifo));
	KLOG("Written %d\n",count-retval);	
	dev->writenum++;
	up(&dev->lockGfifo);
	wake_up_interruptible(&dev->rq);
	return retval?retval:total;
}

static void setup_cdev(struct storas_dev *dev, int index)
{
	int err, devno = MKDEV(major, minor + index);
	cdev_init(&dev->cdev, &my_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &my_fops;
	err = cdev_add (&dev->cdev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		KLOG("Error %d adding %s%d", err,MOD_NAME ,index);
}

static int my_init(void)
{
	KLOG( "init ...\n");
	devs.readnum = devs.writenum = 0;
	if (alloc_chrdev_region(&devt, 0,1, MOD_NAME)) {	
		KLOG("failed to allocate minor \n");
		return -EINVAL;
	}
        	
	major = MAJOR(devt);
	minor = MINOR(devt);
	KLOG("my major = %d minor = %d\n",major,minor);
        //INIT_KFIFO(devs.gfifo);
	sema_init(&devs.lockGfifo,1);
	init_waitqueue_head(&devs.rq);
	init_waitqueue_head(&devs.wq);


	if(kfifo_alloc(&devs.gfifo,fifoSize,GFP_KERNEL)){
		KLOG("failed to allocate fifo size : %d\n",fifoSize);
        	unregister_chrdev_region(devt, 1);
		return  -ENOMEM;
	}

        if(kfifo_initialized(&devs.gfifo)){
		KLOG("Global FIFO initialized [ OK ]\n");  	
	}
	else {	
		KLOG("Global FIFO is NOT initialized [ FAIL ]\n");  	
        }
        KLOG("gFIFO %d:%d\n",kfifo_len(&devs.gfifo),kfifo_size(&devs.gfifo));
	devs.data = kmalloc(DATA_SIZE*sizeof(char*),GFP_KERNEL);
	devs.size = DATA_SIZE;
	devs.hi = 0;
	devs.lo = 0;	
	if (!devs.data){
		KLOG("failed to allocate data\n");
		unregister_chrdev_region(devt, 1);
		return  -ENOMEM;
	}
	KLOG("creating cdev\n");
        setup_cdev(&devs,0);       
 	if (proc_create(MOD_NAME, 0, NULL, &my_fops) == NULL) {
 		KLOG("failed to allocate proc sysfs\n");
		cdev_del(&devs.cdev);
                unregister_chrdev_region(devt, 1);

                return  -ENOMEM;
	}
	KLOG("init [ OK ]\n");
	return 0;
}

static void my_exit(void)
{
	remove_proc_entry(MOD_NAME, NULL);
	KLOG("deleting cdev \n");
	kfifo_free(&devs.gfifo);
	cdev_del(&devs.cdev);
	if (devs.data)
		kfree(devs.data);
	KLOG("unregister me Major %d\n",major);
        unregister_chrdev_region(devt, 1);	
	KLOG(" reads %ld, writes %ld \n",devs.readnum,devs.writenum);
	KLOG("exit [ OK ]\n");
}


module_init(my_init);
module_exit(my_exit);

